package com.gitee.dolt.boot.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.gitee.dolt.boot.mapper.SuperMapper;
import com.gitee.dolt.cache.repository.CachePlusOps;
import com.gitee.dolt.core.cache.model.CacheKey;
import com.gitee.dolt.core.cache.model.CacheKeyBuilder;
import com.gitee.dolt.core.exception.InternalServerException;
import com.gitee.dolt.database.util.MybatisUtils;
import com.gitee.dolt.utils.StrUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 基于 CacheOps 实现的 缓存实现
 * 默认的key规则： #{CacheKeyBuilder#key()}:id
 * <p>
 * 1，getByIdCache：新增的方法： 先查缓存，在查db
 * 2，removeById：重写 ServiceImpl 类的方法，删除db后，淘汰缓存
 * 3，removeByIds：重写 ServiceImpl 类的方法，删除db后，淘汰缓存
 * 4，updateAllById： 新增的方法： 修改数据（所有字段）后，淘汰缓存
 * 5，updateById：重写 ServiceImpl 类的方法，修改db后，淘汰缓存
 *
 * @param <M>
 * @param <T>
 * @author LYH
 */
public abstract class SuperCacheServiceImpl<M extends SuperMapper<T>, T> extends SuperServiceImpl<M, T> implements SuperCacheService<T> {

    private static final String ID_KEY = "ID#ID";

    /**
     * 构造器
     */
    public SuperCacheServiceImpl() {
        this.cacheRegister = new ServiceCacheRegister();
    }

    @PostConstruct
    private void init() {
        register(this.cacheRegister);
        // 注入id缓存器
        this.cacheRegister.register(ID_KEY, new ServiceCacheManager<T>(getCacheKeyBuilder(ID_KEY), model -> getId(model)));
        this.cacheRegister.getCacheManagers().forEach(e -> {
            if (e instanceof AbstractServiceCacheManager) {
                ((AbstractServiceCacheManager) e).setCachePlusOps(cachePlusOps);
            }
        });
    }

    @Autowired
    protected CachePlusOps cachePlusOps;

    /**
     * 缓存注册器
     */
    private ServiceCacheRegister cacheRegister;

    /**
     * 提供一个缓存前缀 方便区分服务缓存
     *
     * @return 缓存前缀
     */
    protected abstract String prefix();

    /**
     * 注册缓存器 需要其他缓存器则重写
     *
     * @param register
     */
    protected void register(ServiceCacheRegister register) {
        // 重写方法
    }

    /**
     * 更新时是否更新/设置缓存
     */
    protected boolean updateSetCache() {
        // 重写方法
        return false;
    }

    /**
     * 键构造器
     */
    private Map<Object, CacheKeyBuilder> cacheKeyBuilderMap = new HashMap<>();

    /**
     * 获取缓存键构造器
     *
     * @param region
     * @return
     */
    protected CacheKeyBuilder getCacheKeyBuilder(Object region) {
        String regionStr = Convert.toStr(region);
        if (StrUtil.isEmpty(regionStr)) {
            return null;
        }
        CacheKeyBuilder cacheKeyBuilder = cacheKeyBuilderMap.get(region);
        if (cacheKeyBuilder == null) {
            cacheKeyBuilder = () -> StrUtil.concat(true, SuperCacheServiceImpl.this.prefix(), StrUtil.COLON, regionStr);
            cacheKeyBuilderMap.put(region, cacheKeyBuilder);
        }
        return cacheKeyBuilder;
    }

    protected CacheKeyBuilder getListCacheKeyBuilder() {
        return getCacheKeyBuilder("LIST#LIST");
    }

    /**
     * 获取所有注册了的缓存器
     *
     * @return
     */
    protected Collection<IServiceCacheManager> getCacheManagers() {
        return this.cacheRegister.getCacheManagers();
    }

    /**
     * 获取注册了的缓存器
     *
     * @param key
     * @return
     */
    public IServiceCacheManager getCacheManager(String key) {
        return this.cacheRegister.getCacheManager(key);
    }

    /**
     * 获取id缓存器
     *
     * @return
     */
    protected IServiceCacheManager getIdCacheManager() {
        return getCacheManager(ID_KEY);
    }


    /**
     * 是否使用缓存列表。列表太大勿用
     *
     * @return
     */
    protected boolean allowCacheList() {
        return true;
    }


    @Override
    public List<T> listByCache() {
        if (!allowCacheList()) {
            throw new InternalServerException("列表缓存未被允许");
        }
        CacheKey cacheKey = getListCacheKeyBuilder().key();
        return cachePlusOps.get(cacheKey, k -> list());
    }

    @Override
    public T getByIdCache(Serializable id) {
        IServiceCacheManager cacheManager = getIdCacheManager();
        Function<Serializable, T> loader = this::getById;
        return (T) cacheManager.getByKey(id, loader);
    }

    @Override
    public T getByKey(CacheKey key, Function<CacheKey, T> loader) {
        return cachePlusOps.get(key, loader);
    }

    @Override
    public List<T> findByIds(Collection<? extends Serializable> ids, Function<Collection<? extends Serializable>, Collection<T>> loader) {
        IServiceCacheManager cacheManager = getIdCacheManager();
        if (loader == null) {
            loader = this::listByIds;
        }
        return cacheManager.findByKeys(ids, loader);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeById(Serializable id) {
        boolean bool = super.removeById(id);
        delCache(id);
        return bool;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Collection<? extends Serializable> idList) {
        if (CollUtil.isEmpty(idList)) {
            return true;
        }
        boolean flag = super.removeByIds(idList);
        delCache(idList);
        return flag;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(T model) {
        boolean save = super.save(model);
        setCache(model, true);
        return save;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateAllById(T model) {
        delCache(model);
        boolean updateBool = super.updateAllById(model);
        if (updateSetCache()) {
            setCacheById(getId(model), false);
        }
        return updateBool;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(T model) {
        delCache(model);
        boolean updateBool = super.updateById(model);
        if (updateSetCache()) {
            setCacheById(getId(model), false);
        }
        return updateBool;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        delListCache();
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            sqlSession.insert(sqlStatement, entity);

            // 设置缓存
            setCache(entity);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");

        delListCache();
        BiPredicate<SqlSession, T> predicate = (sqlSession, entity) -> {
            Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
            return StringUtils.checkValNull(idVal)
                    || CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
        };

        BiConsumer<SqlSession, T> consumer = (sqlSession, entity) -> {
            // 清理缓存
            delCache(entity);
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
        };

        String sqlStatement = SqlHelper.getSqlStatement(this.mapperClass, SqlMethod.INSERT_ONE);
        List<Serializable> ids = new ArrayList<>();
        boolean flag = SqlHelper.executeBatch(getEntityClass(), log, entityList, batchSize, (sqlSession, entity) -> {
            if (predicate.test(sqlSession, entity)) {
                sqlSession.insert(sqlStatement, entity);
                // 设置缓存
                setCache(entity);
            } else {
                consumer.accept(sqlSession, entity);
                ids.add(getId(entity));
            }
        });
        if (updateSetCache()) {
            setCacheByIds(ids, false);
        }
        return flag;


    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateBatchById(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
        delListCache();
        List<Serializable> ids = entityList.stream().map(this::getId).collect(Collectors.toList());
        boolean flag = executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            // 清理缓存
            delCache(entity);
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
        if (updateSetCache()) {
            setCacheByIds(ids, false);
        }
        return flag;
    }

    @Override
    public void refreshCache() {
        List<T> list = list();
        if (allowCacheList()) {
            cachePlusOps.set(getListCacheKeyBuilder().key(), list);
        }
        list.forEach(e -> setCache(e, false));
    }

    @Override
    public void clearCache() {
        delListCache();
        Set<String> keys = cachePlusOps.keys(getCacheKeyBuilder(StrUtils.ASTERISK).key().getKey());
        cachePlusOps.del(ArrayUtil.toArray(keys, String.class));
    }

    protected void delListCache() {
        if (allowCacheList()) {
            CacheKey cacheKey = getListCacheKeyBuilder().key();
            cachePlusOps.del(cacheKey);
        }
    }

    protected void delCache(Serializable... ids) {
        delCache(Arrays.asList(ids));
    }

    protected void delCache(Collection<? extends Serializable> idList) {
        delListCache();
        List<T> models = findByIds(idList, null);
        getCacheManagers().forEach(e -> e.delCache(models));
    }

    protected void delCache(T model) {
        delListCache();
        Object id = getId(model);
        if (id == null) {
            return;
        }
        T oldModel = getByIdCache((Serializable) id);
        getCacheManagers().forEach(e -> e.delCache(oldModel));
    }

    protected void setCache(T model) {
        setCache(model, true);
    }

    protected void setCacheById(Serializable id, boolean delList) {
        if (delList) {
            delListCache();
        }
        T model = super.getById(id);
        if (model == null) {
            return;
        }
        getCacheManagers().forEach(e -> e.setCache(model));
    }

    protected void setCacheByIds(List<Serializable> ids, boolean delList) {
        if (delList) {
            delListCache();
        }
        List<T> models = super.listByIds(ids);
        if (CollUtil.isEmpty(models)) {
            return;
        }
        getCacheManagers().forEach(e -> models.forEach(r -> e.setCache(r)));
    }

    protected void setCache(T model, boolean delList) {
        if (delList) {
            delListCache();
        }
        Object id = getId(model);
        if (id == null) {
            return;
        }
        getCacheManagers().forEach(e -> e.setCache(model));
    }

    protected Serializable getId(T model) {
        return (Serializable) MybatisUtils.getId(model, getEntityClass());
    }

}
